package pl.put.to.classification.data;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.regex.Pattern;

import Jama.Matrix;

public class DataSet {
	private static final Pattern COMMA_PATTERN = Pattern.compile("\\s*,\\s*");

	public static int determineY(ClassValueEnum classValue) {
		int y = classValue == ClassValueEnum.NEGATIVE ? -1 : 1;

		return y;
	}

	private List<DataInstance> instances;
	private Random generator;

	public DataSet(List<DataInstance> instances) {
		this.instances = instances;
		generator = new Random();
	}

	public DataSet(String dataFilePath) throws IOException {
		instances = new ArrayList<DataInstance>();
		generator = new Random();
		
		File dataFile = new File(dataFilePath);

		if (dataFile.exists() && dataFile.isFile()) {
			readFromFile(dataFile);
		} else {
			throw new IOException("Wrong file path.");
		}
	}

	public int getNumberOfVariables() {
		int result = 0;

		if (instances != null && !instances.isEmpty()) {
			result = instances.get(0).getVariableValues().size();
		}

		return result;
	}

	public int getNumberOfInstances() {
		int result = instances.size();

		return result;
	}

	public void standarizeData() {
		List<DataInstance> standarizedInstnces = new ArrayList<DataInstance>();

		for (DataInstance instance : instances) {
			List<Double> standarizedValues = new ArrayList<Double>();

			double mean = calculateMean(instance.getVariableValues());
			double deviation = calculateStandardDeviation(instance.getVariableValues(), mean);

			for (Double value : instance.getVariableValues()) {
				standarizedValues.add((value - mean) / deviation);
			}

			DataInstance standarizedInstance = new DataInstance(standarizedValues, instance.getClassVariableValue());
			standarizedInstnces.add(standarizedInstance);
		}

		instances = standarizedInstnces;
	}

	private double calculateMean(List<Double> values) {
		double mean = 0;

		for (Double value : values) {
			mean += value;
		}

		mean = mean / values.size();

		return mean;
	}

	private double calculateStandardDeviation(List<Double> values, double meanValue) {
		double deviation = 0;

		for (Double value : values) {
			deviation += Math.pow(value - meanValue, 2);
		}

		deviation = Math.sqrt(((double) 1 / (values.size() - 1)) * deviation);

		return deviation;
	}

	private void readFromFile(File file) throws IOException {
		Reader fileReader = new FileReader(file);
		BufferedReader bufferedReader = new BufferedReader(fileReader);

		try {
			String line = null;

			while ((line = bufferedReader.readLine()) != null) {
				fillVariableValues(line);
			}
		} catch (NumberFormatException e) {
			System.out.println("Some data is not numerical.");
			e.printStackTrace();
		} finally {
			bufferedReader.close();
		}
	}

	private void fillVariableValues(String line) {
		String[] valuesFromFile = COMMA_PATTERN.split(line);

		List<Double> variableValues = new ArrayList<Double>();
		ClassValueEnum classVariableValue = null;

		for (String valueText : valuesFromFile) {
			double value = Double.valueOf(valueText);
			variableValues.add(value);
		}

		double lastValue = variableValues.remove(variableValues.size() - 1);
		classVariableValue = ClassValueEnum.values()[(int) lastValue];

		DataInstance instance = new DataInstance(variableValues, classVariableValue);
		instances.add(instance);
	}

	public List<DataInstance> getInstances() {
		return instances;
	}
	
	public DataInstance getRandomInstance() {
		int lp = generator.nextInt(getNumberOfInstances());
		
		DataInstance instance = instances.get(lp);
		instances.remove(lp);
		
		return instance;
	}
	
	public Matrix getXMatrix() {
		double[][] wArray = new double[this.getNumberOfInstances()][this.getNumberOfVariables()];
		
		int i = 0;
		for (DataInstance instance : this.getInstances()) {
			List<Double> list = instance.getVariableValues();
			
			double[] array = new double[list.size()];

			for (int j = 0; j < list.size(); ++j) {
				array[j] = list.get(j);
			}
			
			wArray[i] = array;
		}
		
		Matrix w = new Matrix(wArray);

		return w;
	}


}
